package practice

import (
	"fmt"
	"strconv"
	"time"
)

func Main_goroutine_channel_use() {
	//test1()
	//test3()
	//deadlock()
	//test4()
	//test5()
	//test6()
	//test7()
	//test8()
	//test9()
	//test10()
	test11()
}

//死锁
func deadlock() {
	//如果Goroutine正在等待从通道接收数据，那么另一些Goroutine将会在该通道上写入数据，否则程序将会死锁
	ch := make(chan int)
	ch <- 5
}

//select+time.After()
func test11() {
	/*
	   分支语句：if，switch，select
	   select 语句类似于 switch 语句，
	       但是select会随机执行一个可运行的case。
	       如果没有case可运行，它将阻塞，直到有case可运行。
	*/

	ch1 := make(chan int)
	ch2 := make(chan int)

	//go func() {
	//  ch1 <- 100
	//}()

	//通过channel+select+time.After()优雅地控制协程超时,类似C#中新开Task并设置超时时间
	select {
	case <-ch1:
		fmt.Println("case1可以执行。。")
	case <-ch2:
		fmt.Println("case2可以执行。。")
	case <-time.After(3 * time.Second):
		fmt.Println("case3执行。。timeout。。")

		//default:
		//  fmt.Println("执行了default。。")
	}
}

func test10() {
	/*
	   func After(d Duration) <-chan Time
	       返回一个通道：chan，存储的是d时间间隔后的当前时间。
	*/
	ch1 := time.After(3 * time.Second) //3s后
	fmt.Println(time.Now())            //2019-08-15 09:56:41.529883 +0800 CST m=+0.000465158

	//阻塞3s
	time2 := <-ch1
	fmt.Println(time2) //2019-08-15 09:56:44.532047 +0800 CST m=+3.002662179
}

//timer.Stop()
func test9() {
	//新建计时器，5秒后触发
	timer2 := time.NewTimer(5 * time.Second)

	//新开启一个协程来处理触发后的事件
	go func() {
		//等触上面5秒到期后触发
		<-timer2.C
		fmt.Println("Timer 2 结束。。")
	}()

	//由于上面的等待信号是在新协程中，所以代码会继续往下执行，停掉计时器
	time.Sleep(3 * time.Second)
	stop := timer2.Stop()
	if stop {
		fmt.Println("Timer 2 停止。。")
	}
}

//time.NewTimer()计时器
func test8() {
	//在应对select处理多个channel的超时、单channel读写的超时等情形时尤为方便

	/*
	   1.func NewTimer(d Duration) *Timer
	       创建一个计时器：d时间以后触发，go触发计时器的方法比较特别，就是在计时器的channel中发送值
	*/

	//新建一个计时器：timer
	timer := time.NewTimer(3 * time.Second)
	fmt.Println(time.Now()) //2019-08-15 10:41:21.800768 +0800 CST m=+0.000461190

	//此处在等待channel中的信号
	ch2 := timer.C //<-chan time.Time

	//执行此段代码时会阻塞3秒
	fmt.Println(<-ch2) //2019-08-15 10:41:24.803471 +0800 CST m=+3.003225965
}

//单向通道
func test7() {
	/*
	   单向：定向
	   chan <- T,
	       只支持写，
	   <- chan T,
	       只读

	   用于参数传递：
	*/
	ch1 := make(chan int) //双向，读，写
	//ch2 := make(chan <- int) // 单向，只写，不能读
	//ch3 := make(<- chan int) //单向，只读，不能写
	//ch1 <- 100
	//data :=<-ch1
	//ch2 <- 1000
	//data := <- ch2
	//fmt.Println(data)
	//  <-ch2 //invalid operation: <-ch2 (receive from send-only type chan<- int)
	//ch3 <- 100
	//  <-ch3
	//  ch3 <- 100 //invalid operation: ch3 <- 100 (send to receive-only type <-chan int)

	//go fun1(ch2)
	go fun1(ch1)
	data := <-ch1
	fmt.Println("fun1中写出的数据是：", data)

	//fun2(ch3)
	go fun2(ch1)
	ch1 <- 200
	fmt.Println("main。。over。。")
}

//缓冲通道
func test6() {
	/*
	   非缓存通道：make(chan T)
	   缓存通道：make(chan T ,size)
	       缓存通道，理解为是队列：

	   非缓存，发送还是接受，都是阻塞的
	   缓存通道,缓存区的数据满了，才会阻塞状态。。

	*/
	ch1 := make(chan int)           //非缓存的通道
	fmt.Println(len(ch1), cap(ch1)) //0 0
	//ch1 <- 100//阻塞的，需要其他的goroutine解除阻塞，否则deadlock

	ch2 := make(chan int, 5)        //缓存的通道，缓存区大小是5
	fmt.Println(len(ch2), cap(ch2)) //0 5
	ch2 <- 100                      //
	fmt.Println(len(ch2), cap(ch2)) //1 5

	//ch2 <- 200
	//ch2 <- 300
	//ch2 <- 400
	//ch2 <- 500
	//ch2 <- 600
	fmt.Println("--------------")
	ch3 := make(chan string, 4)
	go sendData3(ch3)
	for {
		time.Sleep(time.Second)
		v, ok := <-ch3
		if !ok {
			fmt.Println("读完了，，", ok)
			break
		}
		fmt.Println("\t读取的数据是：", v)
	}

	fmt.Println("main...over...")
}

//for range形式从通道接收值直到通道关闭
func test5() {
	ch1 := make(chan int)
	go sendData2(ch1)
	// for循环的for range形式可用于从通道接收值，直到它关闭为止。
	for v := range ch1 {
		fmt.Println("读取数据：", v)
	}
	fmt.Println("main..over.....")
}

//通道关闭
func test4() {
	ch1 := make(chan int)
	go sendData(ch1)
	/*
	   子goroutine，写出数据10个
	           每写一个，阻塞一次，主程序读取一次，解除阻塞

	   主goroutine：循环读
	           每次读取一个，堵塞一次，子程序，写出一个，解除阻塞

	   发送发，关闭通道的--->接收方，接收到的数据是该类型的零值，以及false
	*/
	//主程序中获取通道的数据
	for {
		time.Sleep(time.Second)
		v, ok := <-ch1 //其他goroutine，显示的调用close方法关闭通道。
		if !ok {
			fmt.Println("已经读取了所有的数据，", ok)
			break
		}
		fmt.Println("取出数据：", v, ok)
	}

	fmt.Println("main...over....")
}

//2个goroutine等待
func test3() {
	number := 589
	sqrch := make(chan int)
	cubech := make(chan int)
	go calcSquares(number, sqrch)
	go calcCubes(number, cubech)

	//等待上面2个goroutine执行完成
	squares, cubes := <-sqrch, <-cubech
	fmt.Println("Final output", squares+cubes)
}

//goroutine阻塞
func test2() {
	ch1 := make(chan int)
	done := make(chan bool)
	go func() {
		fmt.Println("子goroutine执行。。。")
		time.Sleep(3 * time.Second)
		data := <-ch1 // 从通道中读取数据,此时将阻塞知道main写入
		fmt.Println("data：", data)
		done <- true
	}()

	// 向通道中写数据,子func中将中止阻塞
	time.Sleep(5 * time.Second)
	ch1 <- 100

	<-done

	//如果ok的值是true，表示成功的从通道中读取了一个数据value。如果ok是false，这意味着我们正在从一个封闭的通道读取数据
	//v, ok := <-done
	//fmt.Println(v, ok)

	fmt.Println("main。。over")
}

//goroutine使用
func test1() {
	ch1 := make(chan bool)
	fmt.Println(ch1)

	go func() {
		for i := 0; i < 3; i++ {
			fmt.Println("子goroutine中，i：", i)
		}
		// 循环结束后，阻塞向通道中写数据，表示要结束了。。
		ch1 <- true
		fmt.Println("结束。。")

		//以下代码无法完全执行,因为main执行完就退出了
		for i := 0; i < 10; i++ {
			fmt.Println("测试channel后的代码是否执行完才到channel另一端...", i)
			time.Sleep(time.Second)
		}
	}()

	data := <-ch1 // 阻塞从ch1通道中读取数据
	fmt.Println("data-->", data)
	fmt.Println("main。。over。。。。")
}

func sendData(ch1 chan int) {
	// 发送方：10条数据
	for i := 0; i < 10; i++ {
		ch1 <- i //将i写入通道中
	}
	close(ch1) //将ch1通道关闭了。
}

func sendData2(ch1 chan int) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		ch1 <- i
	}
	close(ch1) //通知对方，通道关闭
}

func sendData3(ch3 chan string) {
	for i := 0; i < 10; i++ {
		ch3 <- "数据" + strconv.Itoa(i)
		fmt.Println("子goroutine，写出第", i, "个数据")
	}
	close(ch3)
}

func calcSquares(number int, squareop chan int) {
	sum := 0
	for number != 0 {
		digit := number % 10
		sum += digit * digit
		number /= 10
	}
	squareop <- sum
}

func calcCubes(number int, cubeop chan int) {
	sum := 0
	for number != 0 {
		digit := number % 10
		sum += digit * digit * digit
		number /= 10
	}
	cubeop <- sum
}

//该函数接收，只写的通道
func fun1(ch chan<- int) {
	// 函数内部，对于ch只能写数据，不能读数据
	ch <- 100
	fmt.Println("fun1函数结束。。")
}

func fun2(ch <-chan int) {
	//函数内部，对于ch只能读数据，不能写数据
	data := <-ch
	fmt.Println("fun2函数，从ch中读取的数据是：", data)
}
